#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "myxpm.h"
#include "transf2d.h"

//Buffer size
#define MAXSIZE 256

const int INSIDE = 0; // 0000
const int LEFT   = 1; // 0001
const int RIGHT  = 2; // 0010
const int BOTTOM = 4; // 0100
const int TOP    = 8; // 1000

struct _Color
{
    uint8 r;       //componentele r,g,b
    uint8 g;
    uint8 b;

    char *pchars;          //pointer la combinatia de caractere
};                      //ce corespunde culorii

struct _XPM
{
    uint32 width;    //latimea in numar de pixeli
    uint32 height;   //inaltimea in numar de pixeli
    uint8 cpp;     //numarul de caractere/pixel
    uint32 ncolors;  //numarul de culori utilizate
    Color *colta;          //tabela de culori
    uint32 **data;   // pointer la zona ce contine imaginea
    // width x height locatii pt.
    // indici in colta
};

/*
    (xmin,ymin) si (xmax,ymax) - extremitatile ferestrei
*/
struct _Window
{
    uint32 xmin;
    uint32 ymin;
    uint32 xmax;
    uint32 ymax;
};

Window *setWindow(uint32 wl, uint32 wt, uint32 wr, uint32 wb)
{
    Window *win;
    win = (Window *) malloc(sizeof(Window));
    if (win == NULL) return NULL;

    win->xmin = wl;
    win->ymin = wt;

    win->xmax = wr;
    win->ymax = wb;

    return win;
}

void clearWindow(Window *win)
{
    free(win);
}

/*
   initXPM initializeaza structura asociata unei imagini in
   formatul XPM; Aceasta va aloca memoria necesara pentru
   tabela de culori (in functie de numarul de caractere/pixel
   si de numarul de culori utilizate) si zona de date
   (in functie de latime si inaltime);
*/
XPM *initXPM(uint32 width, uint32 height, uint8 cpp, uint32 ncolors)
{
    int i;
    XPM *imag;

    imag = (XPM *) malloc(sizeof(XPM));
    if (imag == NULL) return NULL;

    imag->width = width;
    imag->height = height;
    imag->cpp = cpp;
    imag->ncolors = ncolors;

    imag->colta = (Color *) malloc(ncolors * sizeof(Color));
    if (imag->colta == NULL)
    {
        freeXPM(imag);
        return NULL;
    }

    imag->data = (uint32 **) malloc(height * sizeof(uint32 *));
    if (imag->data == NULL)
    {
        freeXPM(imag);
        return NULL;
    }

    for (i = 0; i < height; i++)
    {
        imag->data[i] = (uint32 *) calloc(width, sizeof(uint32));
        if (imag->data[i] == NULL)
        {
            freeXPM(imag);
            return NULL;
        }
    }

    return imag;
}

/*
  freeXPM va elibera memoria alocata prin folosirea initXPM
*/
void freeXPM(XPM *imag)
{
    if (imag == NULL) return;

    int i;

    for (i = 0; i < imag->height; i++)
        free(imag->data[i]);

    free(imag->data);
    free(imag->colta);

    free(imag);
}

/*
  putXPMpixel scrie un pixel la coordonatele (x,y) intr-o
  imagine XPM aflata in memorie. Culoarea va fi data de
  numarul acesteia din tabela de culori a structurii XPM
*/
int putXPMpixel(XPM *imag, uint32 x, uint32 y, uint32 colorindex)
{
    if (imag == NULL)
    {
        return -1;
    }

    if (x >= imag->height || y >= imag->width)
    {
        return -2;
    }

    if (colorindex >= imag->ncolors)
    {
        return -3;
    }

    imag->data[x][y] = colorindex;
    return 0;
}

/*
  setXPMcolor asociaza unui element din tabela de culori
  ("*colta" in structura XPM) elementele r,g,b precum si
  sirul de caractere asociat culorii
*/
int setXPMcolor(XPM *imag, uint32 index, uint8 r, uint8 g, uint8 b, char *charpattern)
{
    if (imag == NULL)
    {
        return -1;
    }

    if (strlen(charpattern) != imag->cpp)
    {
        return -2;
    }

    if (index >= imag->ncolors)
    {
        return -3;
    }

    (imag->colta[index]).r = r;
    (imag->colta[index]).g = g;
    (imag->colta[index]).b = b;

    (imag->colta[index]).pchars = charpattern;

    return 0;
}

/*
  writeXPMtofile scrie continutul unui element imagine -
  structura XPM in fisierul XPM identificat prin "filepath".
  Fisierul va respecta formatul XPM
*/
int writeXPMtofile(XPM *imag, char *filepath)
{
    if (imag == NULL || filepath == NULL)
    {
        return -1;
    }

    int i, j;
    FILE *file_xpm;

    file_xpm = fopen(filepath, "wt");
    if (file_xpm == NULL)
    {
        return -2;
    }

    //==================================================================
    fprintf(file_xpm, "/* XPM */\n");
    fprintf(file_xpm, "static char *egc[] = {\n");
    fprintf(file_xpm, "\n");

    //==================================================================
    fprintf(file_xpm, "/* width,height,nrcolors,charsperpixel */\n");
    fprintf(file_xpm, "\" %u %u %u %u \",\n", imag->width, imag->height, imag->ncolors, imag->cpp);
    fprintf(file_xpm, "\n");

    //==================================================================
    fprintf(file_xpm, "/* colors #RRGGBB */\n");
    for (i = 0; i < imag->ncolors; i++)
    {
        fprintf(file_xpm, "\"%s c #%.2X%.2X%.2X\",\n", imag->colta[i].pchars, imag->colta[i].r, imag->colta[i].g, imag->colta[i].b);
    }
    fprintf(file_xpm, "\n");

    //==================================================================
    fprintf(file_xpm, "/* pixels */\n");
    for (i = 0; i < imag->height; i++)
    {
        fprintf(file_xpm, "\"");
        for (j = 0; j < imag->width; j++)
        {
            fprintf(file_xpm, "%s", imag->colta[imag->data[i][j]].pchars);
        }

        fprintf(file_xpm, "\"");
        if (i != imag->height - 1) fprintf(file_xpm, ",");
        fprintf(file_xpm, "\n");
    }

    //==================================================================
    fprintf(file_xpm, "};\n");

    fclose(file_xpm);
    return 0;
}

XPM *cloneXPM(XPM *imag, uint8 copyData)
{
    if (imag == NULL) return NULL;

    int i, j;
    XPM *copy;
    copy = initXPM(imag->width, imag->height, imag->cpp, imag->ncolors);
    for (i = 0; i < imag->ncolors; i++)
    {
        copy->colta[i] = imag->colta[i];
    }

    if (copyData)
    {
        for (i = 0; i < imag->height; i++)
            for (j = 0; j < imag->width; j++)
                copy->data[i][j] = imag->data[i][j];
    }

    return copy;
}

//TODO --
XPM *transfXPM(XPM *imag, Mat3x3 *m)
{
    if (imag == NULL || m == NULL) return NULL;
    XPM *copy = cloneXPM(imag, 0);

    int i, j;
    uint32 _x, _y;

    for (i = 0; i < imag->height; i++)
        for (j = 0; j < imag->width; j++)
        {
            _x = m->elem[0][0] * j + m->elem[0][1] * i + m->elem[0][2];
            _y = m->elem[1][0] * j + m->elem[1][1] * i + m->elem[1][2];

            putXPMpixel(copy, _y, _x, imag->data[i][j]);
        }

    return copy;
}

int line(XPM *imag, uint32 x0, uint32 y0, uint32 x1, uint32 y1,
         uint32 colorindex)
{
    int dx, dy, sx, sy, err, e2, status;
    int cond = 1;

    dx = abs(x1 - x0);
    dy = abs(y1 - y0);

    sx = (x0 < x1) ? 1 : -1;
    sy = (y0 < y1) ? 1 : -1;

    err = dx - dy;

    while (cond)
    {
        if ( (status = putXPMpixel(imag, x0, y0, colorindex) < 0) )
            return status;

        if (x0 == x1 && y0 == y1) cond = 0;
        else
        {
            e2 = 2 * err;

            if (e2 > -dy)
            {
                err = err - dy;
                x0 = x0 + sx;
            }

            if (e2 < dx)
            {
                err = err + dx;
                y0 = y0 + sy;
            }
        }
    }

    return 0;
}

int ps_to_xpm(char *inputFileName, char *outputFileName, uint32 width, uint32 height,
              Window *win, Mat3x3 *m)
{
    int win_local = 0;
    if (win == NULL)
    {
        win = setWindow(0, 0, width, height);
        win_local = 1;
    }

    if (m == NULL)
    {
        Mat3x3 a;
        InitTranslat(&a, 0, 0); //matricea identitate
        m = &a;
    }

    FILE *f_in;

    XPM *imag = initXPM(width, height, 1, 2);

    setXPMcolor(imag, 0, 0, 0, 0, "B");
    setXPMcolor(imag, 1, 255, 255, 255, "W");

    f_in = fopen(inputFileName, "rt");
    if (f_in == NULL) return -1;

    char buff[MAXSIZE];
    int f_begin = 0, f_end = 0;

    uint32 x1, y1, x2, y2, xx1, yy1, xx2, yy2;

    while (!feof(f_in))
    {
        fgets(buff, MAXSIZE, f_in);
        if (!feof(f_in) && !f_end)
        {
            buff[strlen(buff) - 1] = '\0';

            if (*buff != '\0')
            {
                if (strcmp("%%%END", buff) == 0) f_end = 1;

                if (f_begin && !f_end)
                {
                    sscanf(buff, "%d %d %d %d Line", &x1, &y1, &x2, &y2);
                    if (ClipLine(x1, y1, x2, y2, &xx1, &yy1, &xx2, &yy2, *win))
                    {
                        //printf("%u %u %u %u\n", xx1, yy1, xx2, yy2);
                        line(imag, height - yy1 - 1, xx1, height - yy2 - 1, xx2, 1);
                    }
                }

                if (strcmp("%%%BEGIN", buff) == 0) f_begin = 1;
            }
        }
    }

    fclose(f_in);

    XPM *final = NULL;

    if (m != NULL)
        final = transfXPM(imag, m);
    else
        final = imag;

    if ( (writeXPMtofile(final, outputFileName)) < 0) return -2;

    freeXPM(imag);
    if (m != NULL) freeXPM(final);

    if (win_local) free(win);
    return 0;
}

/*
    Compute the bit code for a point (x, y) using the clip rectangle
    bounded diagonally by (xmin, ymin), and (xmax, ymax)
*/
int ComputeOutCode(uint32 x, uint32 y, Window win)
{
    int code;

    code = INSIDE;          // initialised as being inside of clip window

    if (x < win.xmin)           // to the left of clip window
        code |= LEFT;
    else if (x > win.xmax)      // to the right of clip window
        code |= RIGHT;
    if (y < win.ymin)           // below the clip window
        code |= BOTTOM;
    else if (y > win.ymax)      // above the clip window
        code |= TOP;

    return code;
}

/*
    ClipLine va realiza operatia de "Clipping" folosind algoritmul // Cohen-Sutherland.
    Segmentul de dreapta ce va fi decupat este definit
    de punctele (x1,y1), (x2,y2). Rezultatul operatiei va fi scris la
    adresele indicate de xx1, yy1 (primul punct); xx2, yy2
    (cel de-al doilea punct). Functia va returna "false" daca segmentul
    se afla in afara ferestrei, respectiv "true" in celelalte cazuri.
*/
int ClipLine(uint32 x1, uint32 y1, uint32 x2, uint32 y2,
             uint32 *xx1, uint32 *yy1, uint32 *xx2, uint32 *yy2,
             Window win)
{
    // compute outcodes for P0, P1, and whatever point lies outside the clip rectangle
    int outcode0 = ComputeOutCode(x1, y1, win);
    int outcode1 = ComputeOutCode(x2, y2, win);
    int accept = 0;

    while (1)
    {
        if (!(outcode0 | outcode1))   // Bitwise OR is 0. Trivially accept and get out of loop
        {
            accept = 1;
            break;
        }
        else if (outcode0 & outcode1)     // Bitwise AND is not 0. Trivially reject and get out of loop
        {
            break; //segmentul se afla in afara ferestrei
        }
        else
        {
            // failed both tests, so calculate the line segment to clip
            // from an outside point to an intersection with clip edge
            uint32 x, y;

            // At least one endpoint is outside the clip rectangle; pick it.
            int outcodeOut = outcode0 ? outcode0 : outcode1;

            // Now find the intersection point;
            // use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
            if (outcodeOut & TOP)             // point is above the clip rectangle
            {
                x = x1 + (x2 - x1) * (win.ymax - y1) / (y2 - y1);
                y = win.ymax;
            }
            else if (outcodeOut & BOTTOM)     // point is below the clip rectangle
            {
                x = x1 + (x2 - x1) * (win.ymin - y1) / (y2 - y1);
                y = win.ymin;
            }
            else if (outcodeOut & RIGHT)      // point is to the right of clip rectangle
            {
                y = y1 + (y2 - y1) * (win.xmax - x1) / (x2 - x1);
                x = win.xmax;
            }
            else if (outcodeOut & LEFT)       // point is to the left of clip rectangle
            {
                y = y1 + (y2 - y1) * (win.xmin - x1) / (x2 - x1);
                x = win.xmin;
            }
            // Now we move outside point to intersection point to clip
            // and get ready for next pass.
            if (outcodeOut == outcode0)
            {
                x1 = x;
                y1 = y;
                outcode0 = ComputeOutCode(x1, y1, win);
            }
            else
            {
                x2 = x;
                y2 = y;
                outcode1 = ComputeOutCode(x2, y2, win);
            }
        }
    }

    if (accept)
    {
        *xx1 = x1;
        *yy1 = y1;

        *xx2 = x2;
        *yy2 = y2;

        return 1;
    }

    return 0;
}
